using System;
using System.Numerics;
using Silk.NET.OpenGLES;
using TTEngine.Graphics;
using TTEngine.Resource;
using TTEngine.TTMath;
using voidbox;

namespace TTEngine.Graphics.Render.PackedTexture
{
    public class PackedTextureInstanceBatcher : IRender,IBatcher
    {
        private const int ElementInstSize = 32;
        private Material material;
        private Mesh mesh;
        private PackedTextureGroup packedElement;
        private uint elemInstCount;
        private byte[] elemInstBufData;
        private bool elemInstDirty;
        private int lastTag;
        Resource.ResourcePackage _package;
        public PackedTextureInstanceBatcher(Resource.ResourcePackage package)
        {
            this._package = package;

            material = new Material(_package, ResourceAll.GetNamedResource<ShaderProgram>("inst_tbo"));
            material.UpdateMatModel();

            ElemInstInit();

            mesh = new Mesh(this._package);
            mesh.UpdateVertexFormat(GetFormatVertexUbo());
            InitDrawMesh();
        }

        private static VertexFormat instUbo;
        private static VertexFormat GetFormatVertexUbo()
        {
            if (instUbo == null)
            {
                var vecf = new VertexFormat("Vertex_InstFull");
                vecf.VBOs.Add(new VBOInfo());
                vecf.VBOs[0].Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false)); // basemesh uv only

                vecf.VBOs.Add(new VBOInfo());
                vecf.VBOs[1].VertexAttribDivisor = 1; // instanced data
                // vbo 存在四字节对齐要求
                vecf.VBOs[1].Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 4, false)); // Pos xyz + rotate
                vecf.VBOs[1].Attribs.Add(new VertexAttribItem(VertexAttribPointerType.Float, 2, false)); // Scale
                vecf.VBOs[1].Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedByte, 4, true)); // color
                vecf.VBOs[1].Attribs.Add(new VertexAttribItem(VertexAttribPointerType.UnsignedShort, 2, false)); // INSTid&ext
                instUbo = vecf;
                vecf.Update();
            }
            return instUbo;
        }

        public void SetPackElement(PackedTextureGroup tex)
        {
            packedElement = tex;

            material._uniformTexs["texRGBA"].Value = tex.PackedRGBA.Texture;
            material._uniformTexs["texGray"].Value = tex.PackedGray.Texture;
            var uni = material._uniformTexs["texelem"];
            if (uni != null)
                uni.Value = tex.ElemTex;
        }

        public PackedTextureGroup GetPackElement()
        {
            return packedElement;
        }

        private void ElemInstInit()
        {
            elemInstBufData = new byte[1024 * ElementInstSize];
            elemInstCount = 0;
            elemInstDirty = false;
        }

        public int GetElementInstCount()
        {
            return (int)elemInstCount;
        }

        public void ClearElementInst()
        {
            elemInstCount = 0;
        }

        public int AddElementInst(PackedTextureElementInst elem)
        {
            int index = (int)elemInstCount;
            elemInstCount++;
            WriteElementInst(elem, index);
            return index;
        }

        public void WriteElementInst(PackedTextureElementInst elem, int index)
        {
            if (index * ElementInstSize >= elemInstBufData.Length)
            {
                // 满了,扩容
                var newArr = new byte[(1024 + index) * ElementInstSize];
                Array.Copy(elemInstBufData, newArr, elemInstBufData.Length);
                elemInstBufData = newArr;
            }

            unsafe
            {
                fixed (void* ptr = elemInstBufData)
                {
                    var size = sizeof(PackedTextureElementInst);
                    if (size != ElementInstSize) throw new Exception("error lenght");

                    PackedTextureElementInst* instptr = (PackedTextureElementInst*)ptr;
                    instptr[index] = elem;
                }
            }

            //int byteIndex = index * ElementInstSize;
            //BitConverter.TryWriteBytes(new Span<byte>(elemInstBufData, byteIndex, 4), elem.pos.X);
            //BitConverter.TryWriteBytes(new Span<byte>(elemInstBufData, byteIndex + 4, 4), elem.pos.Y);
            //BitConverter.TryWriteBytes(new Span<byte>(elemInstBufData, byteIndex + 8, 4), elem.pos.Z);
            //BitConverter.TryWriteBytes(new Span<byte>(elemInstBufData, byteIndex + 12, 4), elem.rotate);
            //BitConverter.TryWriteBytes(new Span<byte>(elemInstBufData, byteIndex + 16, 4), elem.scale.X);
            //BitConverter.TryWriteBytes(new Span<byte>(elemInstBufData, byteIndex + 20, 4), elem.scale.Y);
            //elemInstBufData[byteIndex + 24] = (byte)(elem.color.r * 255);
            //elemInstBufData[byteIndex + 25] = (byte)(elem.color.g * 255);
            //elemInstBufData[byteIndex + 26] = (byte)(elem.color.b * 255);
            //elemInstBufData[byteIndex + 27] = (byte)(elem.color.a * 255);
            //BitConverter.TryWriteBytes(new Span<byte>(elemInstBufData, byteIndex + 28, 2), elem.ElemID);
            elemInstDirty = true;
        }

        public PackedTextureElementInst GetElementInst(int index)
        {
            //int byteIndex = index * ElementInstSize;
            var elem = new PackedTextureElementInst
            {
                pos = new Vector3(0, 0, 0),
                scale = new Vector2(0, 0),
                color = new Color(0, 0, 0, 0)
            };
            unsafe
            {
                fixed (void* ptr = elemInstBufData)
                {
                    var size = sizeof(PackedTextureElementInst);
                    if (size != ElementInstSize) throw new Exception("error lenght");

                    PackedTextureElementInst* instptr = (PackedTextureElementInst*)ptr;
                    return instptr[index];
                }
            }
            //elem.pos.X = BitConverter.ToSingle(elemInstBufData, byteIndex);
            //elem.pos.Y = BitConverter.ToSingle(elemInstBufData, byteIndex + 4);
            //elem.pos.Z = BitConverter.ToSingle(elemInstBufData, byteIndex + 8);
            //elem.rotate = BitConverter.ToSingle(elemInstBufData, byteIndex + 12);
            //elem.scale.X = BitConverter.ToSingle(elemInstBufData, byteIndex + 16);
            //elem.scale.Y = BitConverter.ToSingle(elemInstBufData, byteIndex + 20);
            //elem.color.r = elemInstBufData[byteIndex + 24] / 255.0f;
            //elem.color.g = elemInstBufData[byteIndex + 25] / 255.0f;
            //elem.color.b = elemInstBufData[byteIndex + 26] / 255.0f;
            //elem.color.a = elemInstBufData[byteIndex + 27] / 255.0f;
            //elem.ElemID = BitConverter.ToUInt16(elemInstBufData, byteIndex + 28);

            //return elem;
        }

        private void InitDrawMesh()
        {
            int stride = (int)mesh.GetVertexFormat().VBOs[0].Stride;
            var vertexData = new byte[stride * 6];
            var datavbo = new Span<byte>(vertexData);

            // 设置顶点数据
            BitConverter.TryWriteBytes(datavbo.Slice(0 * stride, 4), -1.0f); // x
            BitConverter.TryWriteBytes(datavbo.Slice(0 * stride + 4, 4), -1.0f); // y

            BitConverter.TryWriteBytes(datavbo.Slice(1 * stride, 4), 1.0f); // x
            BitConverter.TryWriteBytes(datavbo.Slice(1 * stride + 4, 4), -1.0f); // y

            BitConverter.TryWriteBytes(datavbo.Slice(2 * stride, 4), -1.0f); // x
            BitConverter.TryWriteBytes(datavbo.Slice(2 * stride + 4, 4), 1.0f); // y

            BitConverter.TryWriteBytes(datavbo.Slice(3 * stride, 4), -1.0f); // x
            BitConverter.TryWriteBytes(datavbo.Slice(3 * stride + 4, 4), 1.0f); // y

            BitConverter.TryWriteBytes(datavbo.Slice(4 * stride, 4), 1.0f); // x
            BitConverter.TryWriteBytes(datavbo.Slice(4 * stride + 4, 4), -1.0f); // y

            BitConverter.TryWriteBytes(datavbo.Slice(5 * stride, 4), 1.0f); // x
            BitConverter.TryWriteBytes(datavbo.Slice(5 * stride + 4, 4), 1.0f); // y

            mesh.UploadVertexBuffer(0, vertexData, false, vertexData.Length);
        }

        public void UpdateMesh()
        {


            packedElement.ApplyTextureData();

            if (elemInstDirty)
            {
                mesh.UploadVertexBuffer(1, elemInstBufData, true, elemInstBufData.Length);
                elemInstDirty = false;
            }

            mesh.InstanceCount = elemInstCount;


        }
        public void BeginDraw()
        {
            ClearElementInst();
        }
        public void EndDraw()
        {
            UpdateTarget();
            UpdateMesh();
            Render();
            ClearElementInst();
        }
        public void Render()
        {
            mesh.DrawInstanced(material);
            Runtime.activeTarget.RenderInfo.DrawCount++;
            Runtime.activeTarget.RenderInfo.DrawElemCount += mesh.InstanceCount;

        }

        public void UpdateTarget()
        {
            material.UpdateMatProj(Runtime.activeTarget);
            material.UpdateMatView(Runtime.activeCamera.GetViewMatrix());
        }

    }
}